#if defined(PLC_IS_APP) || defined(PLC)

#include "iec_app.h"
#include "rte_abi.h"
#include "plc_abi.h"

#define BUFFER_FREE 0
#define BUFFER_BUSY 1

/*
 *  Logging
 */
#define LOG_LEVELS 4
#define LOG_CRITICAL 0
#define LOG_WARNING 1
#define LOG_INFO 2
#define LOG_DEBUG 3
typedef unsigned short u16;
typedef unsigned char u8;

#ifdef ONLINE_DEBUG
#define __Unpack_case_t(TYPENAME)                                                \
    case TYPENAME##_ENUM:                                                        \
        *flags = ((__IEC_##TYPENAME##_t *)varp)->flags;                          \
        forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value; \
        break;

#define __Unpack_case_p(TYPENAME)                                 \
    case TYPENAME##_O_ENUM:                                       \
        *flags = __IEC_OUTPUT_FLAG;                               \
    case TYPENAME##_P_ENUM:                                       \
        *flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;          \
        *real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value;    \
        forced_value_p = &((__IEC_##TYPENAME##_p *)varp)->fvalue; \
        break;
#else
#define __Unpack_case_t(TYPENAME)                                                \
    case TYPENAME##_ENUM:                                                        \
        *flags = ((__IEC_##TYPENAME##_t *)varp)->flags;                          \
        forced_value_p = *real_value_p = &((__IEC_##TYPENAME##_t *)varp)->value; \
        break;

#define __Unpack_case_p(TYPENAME)                              \
    case TYPENAME##_O_ENUM:                                    \
        *flags = __IEC_OUTPUT_FLAG;                            \
    case TYPENAME##_P_ENUM:                                    \
        *flags |= ((__IEC_##TYPENAME##_p *)varp)->flags;       \
        *real_value_p = ((__IEC_##TYPENAME##_p *)varp)->value; \
        break;
#endif

extern IEC_TIME __CURRENT_TIME;
extern BOOL __DEBUG;
static int debug_locked = 0;
static int _DebugDataAvailable = 0;
extern unsigned long __tick;
extern char *buffer_cursor;
extern char debug_buffer[];
extern long buffer_state;

long AtomicCompareExchange(long *atomicvar, long compared, long exchange)
{
    /* No need for real atomic op on LPC,
     * no possible preemption between debug and PLC */
    long res = *atomicvar;
    if (res == compared)
    {
        *atomicvar = exchange;
    }
    return res;
}

int TryEnterDebugSection(void)
{
    if (!debug_locked && __DEBUG)
    {
        debug_locked = 1;
        return 1;
    }
    return 0;
}

void suspendDebug(int disable)
{
    /* Prevent PLC to enter debug code */
    __DEBUG = !disable;
    debug_locked = !disable;
}

void resumeDebug(void)
{
    /* Let PLC enter debug code */
    __DEBUG = 1;
    debug_locked = 0;
}

void LeaveDebugSection(void)
{
    debug_locked = 0;
}

void InitiateDebugTransfer(void)
{
    /* remember tick */
    _DebugDataAvailable = 1;
}

int WaitDebugData(unsigned long *tick)
{
    *tick = __tick;
    if (_DebugDataAvailable && !debug_locked)
    {
        /* returns 0 on success */
        _DebugDataAvailable = 0;
        return 0;
    }
    return 1;
}

void __cleanup(void);

int stopPLC(void)
{
    __cleanup();
    return 0;
}

void *UnpackVar(const __Unpack_desc_type *dsc, void **real_value_p, char *flags);

typedef int (*__for_each_variable_do_fp)(const dbgvardsc_t *);

typedef int (*__for_each_variable_do_fp_flag)(const dbgvardsc_t *, u8);

void __for_each_variable_do(__for_each_variable_do_fp fp);

unsigned int retain_size;
unsigned int retain_count;
unsigned char __get_type_enum_size(__IEC_types_enum t)
{
    switch (t)
    {
        __ANY(__decl_size_case)
    /* size do not correspond to real struct.
     * only a bool is used to represent state*/
    default:
        break;
    }
    return 0;
}

void *UnpackVar(const __Unpack_desc_type *dsc, void **real_value_p, char *flags)
{
    void *varp = dsc->ptr;
    void *forced_value_p = 0;
    *flags = 0;
    /* find data to copy*/
    switch (dsc->type)
    {
        __ANY(__Unpack_case_t)
        __ANY(__Unpack_case_p)
    default:
        break;
    }
    if (*flags & __IEC_MODIFY_FLAG)
    {
        USINT size = __get_type_enum_size(dsc->type);
        memcpy(real_value_p, forced_value_p, size);
        *flags &= ~__IEC_MODIFY_FLAG;
    }
    if (*flags & __IEC_FORCE_FLAG)
        return forced_value_p;
    return *real_value_p;
}

static unsigned int retain_offset = 0;

void __for_each_variable_do(__for_each_variable_do_fp fp)
{
    for (int i = 0; i < sizeof_dbgvardsc; i++)
    {
        const dbgvardsc_t *dsc = &dbgvardsc[i];
        if (dsc->type != UNKNOWN_ENUM)
            if ((*fp)(dsc) != 0)
            {
                // plc_rte->log_warning("%d: type=%d", i, dsc->type);
            };
    }
}

void Remind(unsigned int offset, unsigned int count, void *p);

int RetainCmp(unsigned int offset, unsigned int count, void *p);

int RemindIterator(const dbgvardsc_t *dsc)
{
    void *real_value_p = 0;
    char flags = 0;
    UnpackVar(dsc, &real_value_p, &flags);

    if (flags & __IEC_RETAIN_FLAG)
    {
        USINT size = __get_type_enum_size(dsc->type);
        /* compute next cursor positon*/
        unsigned int next_retain_offset = retain_offset + size;
        /* if buffer not full */
        Remind(retain_offset, size, real_value_p);
        RetainCmp(retain_offset, size, real_value_p);
        /* increment cursor according size*/
        retain_offset = next_retain_offset;
    }
    return 0;
}

/* GetRetainSizeIterator */
int GetRetainSizeIterator(const dbgvardsc_t *dsc)
{
    void *real_value_p = 0;
    char flags = 0;
    UnpackVar(dsc, &real_value_p, &flags);

    if (flags & __IEC_RETAIN_FLAG)
    {
        USINT size = __get_type_enum_size(dsc->type);
        /* Calc retain buffer size */
        retain_size += size;
        retain_count++;
        // plc_rte->log_debug("0x%x: 0x%x type=%d, size=%d,ptr=0x%x", dsc,real_value_p, dsc->type, size,dsc->ptr);
        return 1;
    }
    return 0;
}

/* Return size of all retain variables */
unsigned int GetRetainSize(void)
{
    retain_size = 0;
    retain_count = 0;
    plc_rte->log_debug("GetRetainSize");
    __for_each_variable_do(GetRetainSizeIterator);
    plc_rte->log_info("retain count: %d , size: %d", retain_count, retain_size);
    return retain_size;
}

extern int CheckRetainBuffer(void);

extern void InitRetain(void);

void startRetainThd();

void ResetDebugVariables(u8);

extern void InitiateDebugTransfer(void);

extern void CleanupRetain(void);

void __cleanup_debug(void)
{
#ifdef ONLINE_DEBUG
    buffer_cursor = debug_buffer;
    InitiateDebugTransfer();
#endif

    CleanupRetain();
}

void __retrieve_debug(void)
{
    //
}

void Retain(unsigned int offset, unsigned int count, void *p);

static inline void BufferIterator(const dbgvardsc_t *dsc, int do_debug)
{
    void *real_value_p = 0;
    void *visible_value_p = 0;
    char flags = 0;

    visible_value_p = UnpackVar(dsc, &real_value_p, &flags);

    if (flags & (__IEC_DEBUG_FLAG | __IEC_RETAIN_FLAG))
    {
        USINT size = __get_type_enum_size(dsc->type);

#ifdef ONLINE_DEBUG
        if (flags & __IEC_DEBUG_FLAG)
        {
            /* copy visible variable to buffer */
            if (do_debug)
            {
                /* compute next cursor positon.
                   No need to check overflow, as BUFFER_SIZE
                   is computed large enough */
                if ((dsc->type == STRING_ENUM) ||
                    (dsc->type == STRING_P_ENUM) ||
                    (dsc->type == STRING_O_ENUM))
                {
                    /* optimization for strings */
                    size = ((STRING *)visible_value_p)->len + 1;
                }
                char *next_cursor = buffer_cursor + size;
                /* copy data to the buffer */
                memcpy(buffer_cursor, visible_value_p, size);
                /* increment cursor according size*/
                buffer_cursor = next_cursor;
            }
            /* re-force real value of outputs (M and Q)*/
            if ((flags & __IEC_FORCE_FLAG) && (flags & __IEC_OUTPUT_FLAG))
            {
                memcpy(real_value_p, visible_value_p, size);
            }
        }
#endif
        if (flags & __IEC_RETAIN_FLAG)
        {
            /* compute next cursor positon*/
            unsigned int next_retain_offset = retain_offset + size;
            /* if buffer not full */
            Retain(retain_offset, size, real_value_p);
            /* increment cursor according size*/
            retain_offset = next_retain_offset;
            retain_size += size;
            retain_count++;
        }
    }
}

int DebugIterator(const dbgvardsc_t *dsc)
{
    BufferIterator(dsc, 1);
    return 0;
}

int RetainIterator(const dbgvardsc_t *dsc)
{

    BufferIterator(dsc, 0);
    return 0;
}

void __publish_debug(void)
{
#ifdef ONLINE_DEBUG
    /* Check there is no running debugger re-configuration */
    if (TryEnterDebugSection())
    {
        /* Lock buffer */
        long latest_state = AtomicCompareExchange(&buffer_state, BUFFER_FREE, BUFFER_BUSY);

        /* If buffer was free */
        if (latest_state == BUFFER_FREE)
        {
            /* Reset buffer cursor */
            buffer_cursor = debug_buffer;
            /* Iterate over all variables to fill debug buffer */
            retain_offset = 0;
            __for_each_variable_do(DebugIterator);

            /* Leave debug section,
             * Trigger asynchronous transmission
             * (returns immediately) */
            InitiateDebugTransfer(); /* size */
        }
        else
        {
            /* when not debugging, do only retain */
            retain_offset = 0;
            retain_size = 0;
            retain_count = 0;
            __for_each_variable_do(RetainIterator);
            // plc_rte->log_info("retain count=%d ,size=%d",retain_count,retain_size);
        }
        LeaveDebugSection();
    }
    else
#endif
    {
        /* when not debugging, do only retain */
        retain_offset = 0;
        retain_size = 0;
        retain_count = 0;
        __for_each_variable_do(RetainIterator);
        // plc_rte->log_info("retain count=%d ,size=%d",retain_count,retain_size);
    }
}

#define __RegisterDebugVariable_case_t(TYPENAME)        \
    case TYPENAME##_ENUM:                               \
        ((__IEC_##TYPENAME##_t *)varp)->flags |= flags; \
        break;
#define __RegisterDebugVariable_case_p(TYPENAME)        \
    case TYPENAME##_P_ENUM:                             \
        ((__IEC_##TYPENAME##_p *)varp)->flags |= flags; \
        break;                                          \
    case TYPENAME##_O_ENUM:                             \
        ((__IEC_##TYPENAME##_p *)varp)->flags |= flags; \
        break;

void RegisterDebugVariable(uint32_t idx)
{
#ifdef ONLINE_DEBUG
    if (idx < plc_app.data->size_dbgvardsc)
    {
        unsigned char flags = __IEC_DEBUG_FLAG;
        const dbgvardsc_t *dsc = &dbgvardsc[idx];
        void *varp = dsc->ptr;
        switch (dsc->type)
        {
            __ANY(__RegisterDebugVariable_case_t)
            __ANY(__RegisterDebugVariable_case_p)
        default:
            rte_log_error("type error: %d", idx);
            break;
        }
    }
    resumeDebug();
#endif
}

#define __RegisterVariableForce_case_t(TYPENAME)                          \
    case TYPENAME##_ENUM:                                                 \
        if (force)                                                        \
        {                                                                 \
            ((__IEC_##TYPENAME##_t *)varp)->flags |= flags;               \
            ((__IEC_##TYPENAME##_t *)varp)->value = *((TYPENAME *)force); \
        }                                                                 \
        else                                                              \
        {                                                                 \
            ((__IEC_##TYPENAME##_t *)varp)->flags &= ~flags;              \
        }                                                                 \
        break;
#define __RegisterVariableForce_case_p(TYPENAME)                             \
    case TYPENAME##_P_ENUM:                                                  \
        if (force)                                                           \
        {                                                                    \
            ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;                  \
            ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);   \
        }                                                                    \
        else                                                                 \
        {                                                                    \
            ((__IEC_##TYPENAME##_p *)varp)->flags &= ~flags;                 \
        }                                                                    \
        break;                                                               \
    case TYPENAME##_O_ENUM:                                                  \
        if (force)                                                           \
        {                                                                    \
            ((__IEC_##TYPENAME##_p *)varp)->flags |= flags;                  \
            ((__IEC_##TYPENAME##_p *)varp)->fvalue = *((TYPENAME *)force);   \
            *(((__IEC_##TYPENAME##_p *)varp)->value) = *((TYPENAME *)force); \
        }                                                                    \
        else                                                                 \
        {                                                                    \
            ((__IEC_##TYPENAME##_p *)varp)->flags &= ~flags;                 \
        }                                                                    \
        break;

void RegisterVariableForce(unsigned int idx, void *force)
{
#ifdef ONLINE_DEBUG
    if (idx < sizeof_dbgvardsc)
    {
        unsigned char flags = __IEC_FORCE_FLAG;
        const dbgvardsc_t *dsc = &dbgvardsc[idx];
        void *varp = dsc->ptr;
        switch (dsc->type)
        {
            __ANY(__RegisterVariableForce_case_t)
            __ANY(__RegisterVariableForce_case_p)
        default:
            break;
        }
    }
#endif
}

#define __ResetDebugVariablesIterator_case_t(TYPENAME)    \
    case TYPENAME##_ENUM:                                 \
        ((__IEC_##TYPENAME##_t *)varp)->flags &= ~(flag); \
        break;

#define __ResetDebugVariablesIterator_case_p(TYPENAME)    \
    case TYPENAME##_P_ENUM:                               \
    case TYPENAME##_O_ENUM:                               \
        ((__IEC_##TYPENAME##_p *)varp)->flags &= ~(flag); \
        break;

int ResetDebugVariablesIterator(const dbgvardsc_t *dsc, u8 flag)
{
#ifdef ONLINE_DEBUG
    /* force debug flag to 0*/
    void *varp = dsc->ptr;
    switch (dsc->type)
    {
        __ANY(__ResetDebugVariablesIterator_case_t)
        __ANY(__ResetDebugVariablesIterator_case_p)
    default:
        break;
    }
#endif
    return 0;
}

void __for_each_variable_do_flag(__for_each_variable_do_fp_flag fp, u8 flag)
{
    for (int i = 0; i < sizeof_dbgvardsc; i++)
    {
        const dbgvardsc_t *dsc = &dbgvardsc[i];
        if (dsc->type != UNKNOWN_ENUM)
            if ((*fp)(dsc, flag) != 0)
            {
                // plc_rte->log_warning("%d: type=%d", i, dsc->type);
            };
    }
}

void ResetDebugVariables(u8 flag)
{
#ifdef ONLINE_DEBUG
    __for_each_variable_do_flag(ResetDebugVariablesIterator, flag);
#endif
}

void FreeDebugData(void)
{
#ifdef ONLINE_DEBUG
    /* atomically mark buffer as free */
    AtomicCompareExchange(
        &buffer_state,
        BUFFER_BUSY,
        BUFFER_FREE);
#endif
}

/* Wait until debug data ready and return pointer to it */
int GetDebugData(uint32_t *tick, uint32_t *size, void **buffer)
{
#ifdef ONLINE_DEBUG
    int wait_error = WaitDebugData(tick);
    if (!wait_error)
    {
        *size = buffer_cursor - debug_buffer;
        *buffer = debug_buffer;
    }
    return wait_error;
#endif
}

void __init_debug(void)
{
    /* init local static vars */
#ifdef ONLINE_DEBUG
    buffer_cursor = debug_buffer;
    buffer_state = BUFFER_FREE;
#else
    rte_log_warning("debug disabled\n");
#endif
    plc_app.data->dbgvardsc = dbgvardsc;
    plc_app.data->size_dbgvardsc = sizeof_dbgvardsc;
    retain_offset = 0;
    InitRetain();
    /* Iterate over all variables to fill debug buffer */
    if (CheckRetainBuffer())
    {
        __for_each_variable_do(RemindIterator);
        rte_log_info("RETAIN memory used");
    }
    else
    {
        rte_log_warning("RETAIN memory invalid - defaults used");
    }
    retain_offset = 0;
#ifdef ONLINE_DEBUG
    ResetDebugVariables(__IEC_DEBUG_FLAG | __IEC_FORCE_FLAG);
#endif
}

#endif
